#include <stdlib.h>
#include <stdio.h>
#define ABS_MASK 0x7FFFFFFFFF
#define NEG_MASK 0x8000000000
#define MUL_MASK 0xFFFFFFFFFF
#define STORLEFT_MASK 0xFF000FFFFF
#define STORRIGHT_MASK 0xFFFFFFF000
#define STOR_MASK 0x0000000000

typedef struct {
    unsigned long int AC : 40;
    unsigned long int MQ : 40;
    unsigned long int MBR : 40;
    void (*operacao)();
    //barramentos internos?
} ALU; //Arithmetic-logic Unit

typedef struct {
    unsigned int IR : 8;
    unsigned int IBR : 20;
    unsigned int PC : 12;
    unsigned int MAR : 12;
    unsigned int leftRequired : 1;
    unsigned int nextInstIBR : 1;
} PCU; //Program Control Unit

ALU ula;
extern PCU uc;

void inicializaULA();
void executa();
void printUlaRegs();
void ADD();
void ADDAbs();
void SUB();
void SUBAbs();
void LOADMQM();
void LOADMQ();
void MUL();
void DIV();
void LSH();
void RSH();
void LOAD();
void LOADNeg();
void LOADAbs();
void LOADAbsNeg();
void JUMPRight();
void JUMPLeft();
void JUMPCondRight();
void JUMPCondLeft();
void STORRight();
void STORLeft();
void STOR();

void inicializaULA() {
    ula.AC = 0;
    ula.MBR = 0;
    ula.MQ = 0;
}

void printUlaRegs() {
    printf("ULA:\n");
    printf("   MBR: %010lX - d: %lu\n", ula.MBR, ula.MBR);
    printf("    AC: %010lX - d: %lu\n", ula.AC, ula.AC);
    printf("    MQ: %010lX - d: %lu\n", ula.MQ, ula.MQ);
}

void executa() {    
    if (uc.MAR == 73)
        printf("achou!");
    (*ula.operacao)();
    //printUlaRegs();
}

// ADD M(X)

void ADD() {
    loadMem();
    ula.AC += ula.MBR;
}

// ADD |M(X)|

void ADDAbs() {
    loadMem();
    ula.AC += (ula.MBR & ABS_MASK);
}

// SUB M(X)

void SUB() {
    loadMem();
    long int aux1 = ula.AC;
    long int aux2 = ula.MBR;
    if (ula.AC > ABS_MASK){
        aux1 &= ABS_MASK;
        aux1 *= -1;        
    }
    if (ula.MBR > ABS_MASK) {
        aux2 &= ABS_MASK;
        aux2 *= -1;
    }
    aux1 -= aux2;
    ula.AC = labs(aux1);
    if (aux1 < 0) {
        ula.AC += NEG_MASK;
    }    
}

// SUB |M(X)|

void SUBAbs() {
    loadMem();
    long int aux1 = ula.AC;
    long int aux2 = (ula.MBR & ABS_MASK);
    if (ula.AC > ABS_MASK){
        aux1 &= ABS_MASK;
        aux1 *= -1;        
    }
    
    aux1 -= aux2;
    ula.AC = labs(aux1);
    if (aux1 < 0) {
        ula.AC += NEG_MASK;
    }
}

// MUL M(X)

void MUL() {
    loadMem();
    long int aux1 = ula.MQ;
    long int aux2 = ula.MBR;    
    if (ula.MQ > ABS_MASK){
        aux1 &= ABS_MASK;
        aux1 *= -1;        
    }
    if (ula.MBR > ABS_MASK) {
        aux2 &= ABS_MASK;
        aux2 *= -1;
    }
    long long int result = aux1 * aux2;
    if (result < 0) {
        ula.AC += NEG_MASK;
        result = llabs(result);
    } else {
        ula.AC = 0;
    }
    
    ula.MQ = result & MUL_MASK;
    result >>= 40;
    ula.AC += result;
}

// DIV M(X)

void DIV() {
    loadMem();
    long int aux1 = ula.AC;
    long int aux2 = ula.MBR;
    if (ula.AC > ABS_MASK){
        aux1 &= ABS_MASK;
        aux1 *= -1;        
    }
    if (ula.MBR > ABS_MASK) {
        aux2 &= ABS_MASK;
        aux2 *= -1;
    }
    ldiv_t result = ldiv(aux1, aux2);        
    if (result.quot < 0) {
        ula.MQ = labs(result.quot);
        ula.MQ += NEG_MASK;
    }
    if (result.rem < 0) {
        ula.AC = labs(result.rem);
        ula.AC += NEG_MASK;
    }
}

// LSH

void LSH() {
    ula.AC <<= 1;
}

// RSH

void RSH() {
    ula.AC >>= 1;
}

//LOAD M(X)

void LOAD() {
    loadMem();
    ula.AC = ula.MBR;
}

//LOAD -M(X)

void LOADNeg() {
    loadMem();
    ula.AC = ula.MBR;
    ula.AC ^= NEG_MASK;

}

//LOAD |M(X)|

void LOADAbs() {
    loadMem();
    ula.AC = ula.MBR;
    ula.AC &= ABS_MASK;
}

//LOAD -|M(X)|

void LOADAbsNeg() {
    loadMem();
    ula.AC = ula.MBR;
    ula.AC &= ABS_MASK;
    ula.AC ^= NEG_MASK;
}

//LOAD MQ

void LOADMQ() {
    ula.AC = ula.MQ;
}

//LOAD MQ,M(X)

void LOADMQM() {
    loadMem();
    ula.MQ = ula.MBR;
}

// JUMP M(X, 0:19)

void JUMPLeft() {
    //loadMem();    
    uc.PC = uc.MAR;
    uc.leftRequired = 1;
    uc.nextInstIBR = 0;
}

// JUMP M(X, 20:39)

void JUMPRight() {
    //loadMem();    
    uc.PC = uc.MAR;
    uc.leftRequired = 0;
    uc.nextInstIBR = 0;
}

// JUMP+ M(X, 0:19)

void JUMPCondLeft() {
    //loadMem();
    if (!(ula.AC & NEG_MASK)) {        
        uc.PC = uc.MAR;
        uc.leftRequired = 1;
        uc.nextInstIBR = 0;
    }
}

// JUMP+ M(X, 20:39)

void JUMPCondRight() {
    //loadMem();
    if (!(ula.AC & NEG_MASK)) {
        uc.PC = uc.MAR;
        uc.leftRequired = 0;
        uc.nextInstIBR = 0;
    }
}

// STOR M(X,8:19)

void STORLeft() {
    ula.MBR = ula.AC;
    ula.MBR <<= 20;
    storMem(STORLEFT_MASK);
}

// STOR M(X,28:39)

void STORRight() {
    ula.MBR = ula.AC;
    storMem(STORRIGHT_MASK);
}

// STOR M(X)

void STOR() {
    ula.MBR = ula.AC;
    storMem(STOR_MASK);
}